/*
 * 2007/05
 * 2007/10
 * 2011/11/17 - 13:47
 */
package org.nettyflux.example.asteroids;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.AffineTransformOp;
import java.awt.geom.AffineTransform;
import java.io.IOException;

/**
 * @author Victor Williams Stafusa da Silva
 */
public class Ball implements ScreenObject {

    public static final int RADIUS = 7;
    private static final double ANGLE_135_DEGREES = (3.0 * Math.PI / 4.0);
    private static final double ANGLE_67_5_DEGREES = (3.0 * Math.PI / 8.0);

    private static final BufferedImage SPRITE;

    static {
        try {
            SPRITE = MaskedImage.load("bola1.bmp", 0xffffff);
        } catch (IOException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private final Paddle paddle;
    private final RenderScreen screen;
    private double x;
    private double y;

    private final double initialX;
    private double vx;
    private double vy;
    private double speed;
    private double angularPhase;
    private double angularSpeed;

    public Ball(int x, int y, RenderScreen screen, Paddle paddle) {
        this.x = x;
        this.y = y;
        speed = 18;
        vx = (Math.sqrt(2) * speed) / 2;
        vy = (Math.sqrt(2) * speed) / 2;
        this.paddle = paddle;
        this.screen = screen;
        initialX = x;
        angularSpeed = Math.random() / 2 + 0.01;
    }

    @Override
    public void move(double deltaT) {

        double minX = RADIUS;
        double maxX = screen.getRenderWidth() - RADIUS;
        double minY = RADIUS;
        double maxY = screen.getRenderHeight() - RADIUS;

        double minPaddleX = paddle.getX1() - RADIUS;
        double maxPaddleX = paddle.getX2() + RADIUS;
        double minPaddleY = paddle.getY1() - RADIUS;
        double maxPaddleY = paddle.getY2() + RADIUS;

        boolean reboundX = false, reboundY = false, reboundPaddle = false, loseLife = false;
        double subDeltaT = 0;
        angularPhase += angularSpeed;
        angularPhase %= (Math.PI * 2);

        while (deltaT != 0.0) {
            reboundX = false; reboundY = false; reboundPaddle = false; loseLife = false;

            // Test right wall collision.
            if (x + vx * deltaT > maxX) {
                subDeltaT = (maxX - x) / vx;
                reboundX = true;
                loseLife = true;

            // Test left wall collision.
            } else if (x + vx * deltaT < minX) {
                subDeltaT = (minX - x) / vx;
                reboundX = true;
            }

            // Test bottom wall collision.
            if (y + vy * deltaT > maxY) {
                subDeltaT = (maxY - y) / vy;
                reboundY = true;

            // Test top wall collision.
            } else if (y + vy * deltaT < minY) {
                subDeltaT = (minY - y) / vy;
                reboundY = true;
            }

            // Test right paddle border collision.
            if (x >= maxPaddleX && x + vx * deltaT < maxPaddleX && y + vy * deltaT < maxPaddleY && y + vy * deltaT > minPaddleY) {
                subDeltaT = (maxPaddleX - x) / vx;
                reboundX = true;

            // Test left paddle border collision.
            } else if (x <= minPaddleX && x + vx * deltaT > minPaddleX && y + vy * deltaT < maxPaddleY && y + vy * deltaT > minPaddleY) {
                subDeltaT = (minPaddleX - x) / vx;
                reboundX = true;
                reboundPaddle = true;
            }

            // Test bottom paddle border collision.
            if (y >= maxPaddleY && y + vy * deltaT < maxPaddleY && x + vx * deltaT < maxPaddleX && x + vx * deltaT > minPaddleX) {
                subDeltaT = (maxPaddleY - y) / vy;
                reboundY = true;

            // Test top paddle border collision.
            } else if (y <= minPaddleY && y + vy * deltaT > minPaddleY && x + vx * deltaT < maxPaddleX && x + vx * deltaT > minPaddleX) {
                subDeltaT = (minPaddleY - y) / vy;
                reboundY = true;
            }

            // No collision. Move freely.
            if (!reboundX && !reboundY && !reboundPaddle) {
                subDeltaT = deltaT;
            }
            deltaT -= subDeltaT;
            if (!reboundPaddle) {
                y += vy * subDeltaT;
                x += vx * subDeltaT;
                if (reboundX) vx = -vx;
                if (reboundY) vy = -vy;
            } else {
                rebound();
            }
            if (loseLife) {
                restart();
                screen.decreaseLifes();
            }
        }
    }

    public void rebound() {
        if (screen.markPoint()) {
            speed *= 1.1;
        }
        int sgn = (vx > 0 ? -1 : vx < 0 ? 1 : 0); // Current speed signal * -1.
        double rnd = (Math.random() * ANGLE_135_DEGREES) - ANGLE_67_5_DEGREES; // -67.5 degrees to +67.5 degrees.
        vx = speed * sgn * Math.cos(rnd);
        vy = speed * Math.sin(rnd);
    }

    public void restart() {
        x = initialX;
    }

    public double getSpeed() {
        return speed;
    }

    public double getAngularPhase() {
        return angularPhase;
    }

    private static final RenderingHints HINTS = new RenderingHints(null);

    static {
        HINTS.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        HINTS.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        HINTS.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        HINTS.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    }

    @Override
    public void draw(Graphics2D g) {
        // drawOval sums a pixel to width and height, fillOval doesn't.
        /*g.setColor(Color.GREEN);
        g.fillOval(round(x) - RAIO, round(y) - RADIUS, 2 * RADIUS + 1, 2 * RADIUS + 1);
        g.setColor(Color.BLUE);
        g.drawOval(round(x) - RAIO, round(y) - RADIUS, 2 * RADIUS, 2 * RADIUS);*/

        AffineTransform at = AffineTransform.getRotateInstance(angularPhase, RADIUS, RADIUS);
        AffineTransformOp ato = new AffineTransformOp(at, HINTS);
        g.drawImage(SPRITE, ato, round(x) - RADIUS, round(y) - RADIUS);
    }

    private static int round(double v) {
        return (int) Math.floor(v + 0.5);
    }
}